#pragma once

#include <vex/core/config.h>
#include <vex/policy/protect_policy.h>
#include <vex/policy/empty_base.h>

namespace vex { namespace functional {

    template< class TClass, class TClassTraits, class TBase = policy::empty_base >
    struct callable_with_invoke_policy : public TBase {
        typedef typename TClassTraits::result_type result_type;

#ifdef VEX_HAS_CPP_11_FEATURE_SET_3
        template<class ...TArgs>
        typename TClassTraits::result_type operator()(TArgs&& ...p_args) const
        {
            return static_cast<TClass const*>(this)->get()->invoke(std::forward<TArgs>(p_args)...);
        }
#else
        typename TClassTraits::result_type operator()() const
        {
            return static_cast<TClass const*>(this)->get()->invoke();
        }

        template<class TArg1>
        typename TClassTraits::result_type operator()(
            TArg1&& p_arg1) const
        {
            return static_cast<TClass const*>(this)->get()->invoke(std::forward<TArg1>(p_arg1));
        }

        template<class TArg1, class TArg2>
        typename TClassTraits::result_type operator()(
            TArg1&& p_arg1, TArg2&& p_arg2) const
        {
            return static_cast<TClass const*>(this)->get()->invoke(
                std::forward<TArg1>(p_arg1), 
                std::forward<TArg2>(p_arg2));
        }

        template<class TArg1, class TArg2, class TArg3>
        typename TClassTraits::result_type operator()(
            TArg1&& p_arg1, TArg2&& p_arg2, TArg3&& p_arg3) const
        {
            return static_cast<TClass const*>(this)->get()->invoke(
                std::forward<TArg1>(p_arg1), 
                std::forward<TArg2>(p_arg2),
                std::forward<TArg3>(p_arg3));
        }

        template<class TArg1, class TArg2, class TArg3, class TArg4>
        typename TClassTraits::result_type operator()(
            TArg1&& p_arg1, TArg2&& p_arg2, TArg3&& p_arg3, TArg4&& p_arg4) const
        {
            return static_cast<TClass const*>(this)->get()->invoke(
                std::forward<TArg1>(p_arg1), 
                std::forward<TArg2>(p_arg2),
                std::forward<TArg3>(p_arg3),
                std::forward<TArg4>(p_arg4));
        }

        template<class TArg1, class TArg2, class TArg3, class TArg4, class TArg5>
        typename TClassTraits::result_type operator()(
            TArg1&& p_arg1, TArg2&& p_arg2, TArg3&& p_arg3, TArg4&& p_arg4, TArg5&& p_arg5) const
        {
            return static_cast<TClass const*>(this)->get()->invoke(
                std::forward<TArg1>(p_arg1), 
                std::forward<TArg2>(p_arg2),
                std::forward<TArg3>(p_arg3),
                std::forward<TArg4>(p_arg4),
                std::forward<TArg5>(p_arg5));
        }
#endif
        VEX_PROTECT_POLICY(callable_with_invoke_policy);
    };
}}